/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * JDBCResultSetTableHeader.java * Created: Oct 30, 2003 * By: Kevin Sit */ package org.openquark.gems.client.valueentry; import java.awt.Component; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragSource; import java.awt.event.InputEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.JTable; import javax.swing.UIManager; import javax.swing.plaf.UIResource; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import org.openquark.gems.client.valueentry.JDBCResultSetEditor.JDBCResultSetDragPointHandler; /** * This is the object which manages the header of the table embedded in a * <code>JDBCResultSetEditor</code>. Users cannot reorder columns in this * table, only resizing is allowed. This header also allows the user to drag * a column and drop it onto an external component. * * This table header class is designed to work with <code>JDBCResultSetEditor</code> only. * * @author ksit */ public class JDBCResultSetTableHeader extends JTableHeader { private static final long serialVersionUID = 8633791832079777878L; /** * The drag geature listener for the table header. This listener verifies * that the gesture event is originated from a mouse event and register * the drag action with the drag source. */ private class JDBCTableHeaderDragGestureListener implements DragGestureListener { /* (non-Javadoc) * @see java.awt.dnd.DragGestureListener#dragGestureRecognized(java.awt.dnd.DragGestureEvent) */ public void dragGestureRecognized(DragGestureEvent dge) { // if the user is attempting to resize the column, then don't try to // start our own drag column event because it will interfere with // JTable's internal drag and drop mechanism! if (getResizingColumn() != null) { return; } // no need to continue if the drag event is not triggered by a mouse if (!(dge.getTriggerEvent() instanceof MouseEvent)) { return; } // don't need to continue if there is no selected column or the // result set is null int[] indices = columnModel.getSelectedColumns(); if (indices.length == 0 || parentEditor.getResultAdapter().getResultSet() == null) { return; } // start the drag now and register listeners for mouse drag motions // if the drag was initiated successfully dragPointHandler.dragColumns(dge, parentEditor, table.getColumnModel()); } } /** * This mouse listener should be registered with the table in order to revert * back to row selection mode when a cell is selected. */ private class JDBCTableMouseListener extends MouseAdapter { /* (non-Javadoc) * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent) */ @Override public void mousePressed(MouseEvent e) { changeToRowSelectionMode(); // resolve the mouse location to a row index int index = table.rowAtPoint(e.getPoint()); if (index < 0) { return; } table.addRowSelectionInterval(index, index); } } /** * This mouse listener should be registered with the table header component * to pick up the Ctrl+Button and Shift+Button drag and drop gesture events. * When this listener is first called, it will register the JDBC table mouse * listener with the parent table. This listener also sets the column * selection mode in the table. */ private class JDBCTableHeaderMouseListener extends MouseAdapter { private boolean selectionEventHandled; /* (non-Javadoc) * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent) */ @Override public void mousePressed(MouseEvent e) { // run the common code first int colIndex = mouseEventOccured(e); if (colIndex < 0) { return; } // ignore the mouse-pressed event if the column is already in the current selection. // This is necessary because we don't want to lose the selection if the user // holds the mouse button down (i.e. starts to drag) without pressing the // Ctrl key or the Shift key. selectionEventHandled = false; if (!table.isColumnSelected(colIndex)) { if (isMultiSelectGesture(e)) { handleMultiSelectMouseGesture(e, colIndex); } else { handleSingleSelectMouseGesture(e, colIndex); } // the flag to the true so that we don't need to handle the selection gesture // in the mouse release event selectionEventHandled = true; } } /* (non-Javadoc) * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) */ @Override public void mouseReleased(MouseEvent e) { // run the common code first int colIndex = mouseEventOccured(e); if (colIndex < 0) { return; } // if the selection is already handled at the time when the mouse button is // pressed, then we should not handle it again in here if (!selectionEventHandled) { if (isMultiSelectGesture(e)) { handleMultiSelectMouseGesture(e, colIndex); } else { handleSingleSelectMouseGesture(e, colIndex); } } } /** * This method is called whenever there is a mouse event occured. * @param e * @return int The index of the column that corresponds to the mouse click location. */ private int mouseEventOccured(MouseEvent e) { // register a listener in the parent table, so that we can re-enable // the row selection mode if (tableMouseListener == null) { tableMouseListener = new JDBCTableMouseListener(); table.addMouseListener(tableMouseListener); } // resolve the mouse location to a column index int index = JDBCResultSetTableHeader.this.columnAtPoint(e.getPoint()); if (index < 0) { return -1; } else { // set up the table to accept column selection, otherwise we cannot // highlight selected columns. changeToColumnSelectionModel(); return index; } } } /** * A table header cell render that supports PLAF and it displays the table * header value as a tool tip as well. This is useful for showing the name * of a really long string. */ private static class TooltipEnabledCellRenderer extends DefaultTableCellRenderer implements UIResource { private static final long serialVersionUID = 5308446012366212919L; /* (non-Javadoc) * @see javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, java.lang.Object, boolean, boolean, int, int) */ @Override public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (table != null) { JTableHeader header = table.getTableHeader(); if (header != null) { setForeground(header.getForeground()); setBackground(header.getBackground()); setFont(header.getFont()); } } String str = (value == null ? "" : value.toString()); setText(str); setToolTipText(str); setBorder(UIManager.getBorder("TableHeader.cellBorder")); return this; } } /** * Parent editor that creates this header. */ private final JDBCResultSetEditor parentEditor; /** * Drag manager associated with this component. */ private final JDBCResultSetDragPointHandler dragPointHandler; /** * Drag manager associated with this component. */ private MouseListener headerMouseListener; /** * Drag manager associated with this component. */ private MouseListener tableMouseListener; /** * Variables used to keep track of selection range indices. Do NOT modify the values of * these two variables! Use <code>beginRangeSelection</code> or <code>endRangeSelection</code> * to manipulate these variables implicitly. */ private int rangeBeginIndex = -1; private int rangeEndIndex = -1; public JDBCResultSetTableHeader( TableColumnModel cm, JDBCResultSetEditor parentEditor, JDBCResultSetDragPointHandler dragPointHandler) { super(cm); this.parentEditor = parentEditor; this.dragPointHandler = dragPointHandler; initializeDragAndDrop(); } /* (non-Javadoc) * @see javax.swing.table.JTableHeader#setReorderingAllowed(boolean) */ @Override public void setReorderingAllowed(boolean reorderingAllowed) { // do nothing: users cannot re-enable the resizing option } /* (non-Javadoc) * @see javax.swing.table.JTableHeader#setResizingColumn(javax.swing.table.TableColumn) */ @Override public void setResizingColumn(TableColumn aColumn) { super.setResizingColumn(aColumn); if (aColumn != null) { changeToRowSelectionMode(); } } /* (non-Javadoc) * @see javax.swing.table.JTableHeader#createDefaultRenderer() */ @Override protected TableCellRenderer createDefaultRenderer() { return new TooltipEnabledCellRenderer(); } /** * Initializes the drag and drop system to allow dragging this component and dropping * it to an external entity. */ private void initializeDragAndDrop() { // in order to support drag and drop, we have to disable reordering because JTable's // reordering is implemented using a mouse pressed listener, which conflicts with // the standard drag and drop API super.setReorderingAllowed(false); // initialize the component as a drag source if (dragPointHandler != null) { // we have to use the COPY_OR_MOVE action because the workspace drop // target only recognizes the MOVE command, but the default gesture // listener links the Ctrl+Button1 gesture to the COPY command DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer( this, DnDConstants.ACTION_COPY_OR_MOVE, new JDBCTableHeaderDragGestureListener()); } if (headerMouseListener == null) { headerMouseListener = new JDBCTableHeaderMouseListener(); addMouseListener(headerMouseListener); } } /** * Returns true if the given mouse event is a mouse gesture that selects multiple * columns from the table. * @param e * @return boolean */ private boolean isMultiSelectGesture(MouseEvent e) { return (e.getModifiers() & (InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)) > 0; } /** * Performs the selection operation on a single column. Caller is responsible * for ensuring the validity of the given column index. * @param e * @param colIndex */ private void handleSingleSelectMouseGesture(MouseEvent e, int colIndex) { table.clearSelection(); beginRangeSelection(colIndex); } /** * Performs the selection operation on more than one columns. Caller is responsible * for ensuring the validity of the given column index. * @param e * @param colIndex */ private void handleMultiSelectMouseGesture(MouseEvent e, int colIndex) { int modifiers = e.getModifiers(); // the Shift key gesture overrides the Ctrl key: even if both Ctrl and Shift keys // are pressed, we are still doing a range selection (i.e. multiple ranges). if ((modifiers & InputEvent.SHIFT_MASK) > 0) { if (rangeBeginIndex >= 0) { // this is a special case: the user had already selected a range, however, // the user did not let go of the Shift key and clicked on another column. // This action should cause the original selection to be cancelled. if (rangeEndIndex >= 0) { table.removeColumnSelectionInterval(rangeBeginIndex, rangeEndIndex); } endRangeSelection(colIndex); } else { // only invoked when this method is first called: since the begin index // is not set, then we fall back to single selection mode even if the mouse // event is a multiselect gesture handleSingleSelectMouseGesture(e, colIndex); } } else if ((modifiers & InputEvent.CTRL_MASK) > 0) { // if the given index already exists in the selection model, then remove it // from the selection model. Otherwise, we assume that the user is trying // to start a new range selection. if (table.isColumnSelected(colIndex)) { table.removeColumnSelectionInterval(colIndex, colIndex); } else { beginRangeSelection(colIndex); } } } /** * Marks the beginning of a range selection. This method attempts to add the * column, referenced by the index, to the selection model and updates the * range selection indices. * @param index */ private void beginRangeSelection(int index) { table.addColumnSelectionInterval(index, index); rangeBeginIndex = index; rangeEndIndex = -1; } /** * Marks the end of a range selection. This method attempts to add the range * of columns to the selection model and updates the range selection indices. * @param index */ private void endRangeSelection(int index) { if (rangeBeginIndex >= 0) { table.addColumnSelectionInterval(rangeBeginIndex, index); rangeEndIndex = index; } } /** * A convenient method for switching the table to row selection mode. Cannot * cannot this method in the constructor. */ private void changeToRowSelectionMode() { table.clearSelection(); table.setRowSelectionAllowed(true); table.getColumnModel().setColumnSelectionAllowed(false); } /** * A convenient method for switching the table to column selection mode. Cannot * cannot this method in the constructor. */ private void changeToColumnSelectionModel() { table.setRowSelectionAllowed(false); table.getColumnModel().setColumnSelectionAllowed(true); } }